//
// Created by chao.li on 2023/11/10.
//

#ifndef SRC_DRAWER_COMPLEX_H
#define SRC_DRAWER_COMPLEX_H

#include "drawer_basic.h"
#include "circle_queue.h"
#include "magic_enum.hpp"

#include <complex>
#include <queue>
#include <chrono>

#define DEBUG 0

using complexd = std::complex<double>;

class drawer_complex
        : public drawer_basic
{
public:
    int STEP = 1;
    int MAX_ITER = 300;
    float MIN_DELTA = 0.00001;
    float MAX_Z_VALUE = 100;

    class cycle_state
    {
    public:
        int cycle = 1;
        static constexpr int divergency = 0;
        static constexpr int convergency = 1;
        static constexpr int shock = 33;

        cycle_state(int cc) : cycle(cc){}
        operator int() { return cycle; }
        std::string name(){
            std::ostringstream ss;
            ss << "cycle_";
            switch (cycle) {
                case cycle_state::convergency:
                    ss << "convergency";
                case cycle_state::shock:
                    ss << "shock";
                case cycle_state::divergency:
                    ss << "divergency";
                default:
                    ss << cycle;
            }
            return ss.str();
        }
    };

    void test()
    {
        auto fun = [](
                const complexd& z, const complexd& zc){
            return z * z + zc;
        };

        auto demo = [&fun, this](
                const complexd& z, const complexd& zc, int count
        ){
            std::cout << "z0: " << z << std::endl;
            std::cout << "zc: " << zc << std::endl;
            complexd z0(z);
            for (int i = 0; i < count; ++i) {
                z0 = fun(z0, zc);
                std::cout << i << " : " << z0 << std::endl;
                if(large_cpx(z0)){
                    // 发散
                    break;
                }
            }
            std::cout << "=========================" << std::endl;
        };

        auto z0 = complexd(0.2, 0.5);
        auto zc = complexd(-0.6, 0);
//        demo(z0, zc, MAX_ITER);
//        std::cout << "res: " << magic_enum::enum_name(test_julia_res(z0, zc, iter_result::convergency)) << std::endl;
//        std::cout << std::endl;
//        z0 = complexd(0.22, 0.5);
//        demo(z0, zc, MAX_ITER);
//        std::cout << "res: " << magic_enum::enum_name(test_julia_res(z0, zc, iter_result::convergency)) << std::endl;
//        std::cout << std::endl;
//
//        z0 = complexd(0.2, 0.2);
//        zc = complexd(-1.1, 0);
//        demo(z0, zc, MAX_ITER);
//        std::cout << "res: " << magic_enum::enum_name(test_julia_res(z0, zc, iter_result::cycle_2)) << std::endl;
//        std::cout << std::endl;
//        z0 = complexd(0.3, 0.2);
//        demo(z0, zc, MAX_ITER);
//        std::cout << "res: " << magic_enum::enum_name(test_julia_res(z0, zc, iter_result::cycle_2)) << std::endl;
//        std::cout << std::endl;
//
//        z0 = complexd(0.2, 0.05);
//        zc = complexd(-1.35, 0);
//        demo(z0, zc, MAX_ITER);
//        std::cout << "res: " << magic_enum::enum_name(test_julia_res(z0, zc, iter_result::cycle_4)) << std::endl;
//        std::cout << std::endl;
//        z0 = complexd(0.2, 0.1);
//        demo(z0, zc, MAX_ITER);
//        std::cout << "res: " << magic_enum::enum_name(test_julia_res(z0, zc, iter_result::cycle_4)) << std::endl;
//        std::cout << std::endl;
//
//        z0 = complexd(0, 0);
//        zc = complexd(-0.37, 0.61);
//        demo(z0, zc, MAX_ITER);
//        std::cout << "res: " << magic_enum::enum_name(test_julia_res(z0, zc, iter_result::cycle_8)) << std::endl;
//        std::cout << std::endl;
//        z0 = complexd(0.2, 0.1);
//        demo(z0, zc, MAX_ITER);
//        std::cout << "res: " << magic_enum::enum_name(test_julia_res(z0, zc, iter_result::cycle_8)) << std::endl;
//        std::cout << std::endl;

//        z0 = complexd(-0.7, 0.0);
//        demo(z0, z0, MAX_ITER);
//        std::cout << "res: " << magic_enum::enum_name(test_julia_res(z0, z0, iter_result::divergency)) << std::endl;

        z0 = complexd(-0.7501, 0.0001);
//        demo(z0, z0, MAX_ITER);
        start_timer();
        cycle_state state = cycle_state::divergency;
        test_julia_res(z0, z0, state);
        end_timer();
    }

    color3 get_julia_col(cycle_state res, int step = 0)
    {
        auto blue = color3(0.3,0.3,1);
        auto green = color3(0.3,1,0.3);

        auto black = const_color::black();
        auto white = const_color::white();

        switch (res.cycle) {
            case cycle_state::convergency:
                return blue;
//            case cycle_state::shock:
//                return color3(1,1,1);
            case cycle_state::divergency:
                return lerp(black, white, pow((float)step / MAX_ITER, 0.3));
            default:
                return lerp(blue, blue, res.cycle/(res.shock-1.));
        }
    }

    bool large_cpx(const complexd& l)
    {
        return abs(l) > MAX_Z_VALUE;
    }

    bool check_convergence(const circle_queue<complexd>& cq, complexd z, cycle_state state)
    {
        int cycle = state.cycle - cycle_state::convergency + 1;
        int pre_step_index = cq.pre_i(cq.end_i(), cycle);
        auto value = cq[pre_step_index];
        auto ll = abs(z - value);
        return ll <= MIN_DELTA;
    }

    void debug_result(const circle_queue<complexd>& cq, complexd z, int i, cycle_state state)
    {
        for(int iter = 0; iter < cq.size(); ++iter){
            auto index = cq.next_i(cq.begin_i(), iter);
            std::cout << i - cq.size() + iter << " : " << cq[index] << std::endl;
        }
        std::cout << i << " : " << z << std::endl;
        std::cout << "res: " << state.name() << std::endl;
    }

    int test_julia_res(const complexd& z0, const complexd& zc, cycle_state& state)
    {
        circle_queue<complexd> cq(cycle_state::shock);

        complexd z = z0;
        cq.push_back(z);
        for (int i = 0; i < MAX_ITER; ++i) {
            z = z * z + zc;
            if(large_cpx(z)){
                // 发散
#if DEBUG
                debug_result(cq, z, i, cycle_state::divergency);
#endif
                state = cycle_state::divergency;
                return i;
            }
//            if(state != cycle_state::divergency){
//                if(check_convergence(cq, z, state)){
//#if DEBUG
//                    debug_result(cq, z, i, state);
//#endif
//                    return i;
//                }
//            }
            // 从大到小检测所有周期
            for (auto ss = cycle_state::shock-1;
                 ss >= 1;
                 ss -- ) {
                if(check_convergence(cq, z, ss)){
#if DEBUG
                    debug_result(cq, z, MAX_ITER, ss);
#endif
                    state = ss;
                    return i;
                }
            }
            //继续下次迭代
            cq.push_back(z);
        }

//        //额外迭代一次，检测所有周期
//        z = z * z + zc;
//        if(state == cycle_state::divergency){
//            // 传入的cycle为0，需要自己寻找周期
//            for (auto ss = cycle_state::convergency;
//                 ss < cycle_state::shock;
//                 ss = cycle_state((int)ss + 1)) {
//                if(check_convergence(cq, z, ss)){
//#if DEBUG
//                    debug_result(cq, z, MAX_ITER, ss);
//#endif
//                    state = ss;
//                    return MAX_ITER / 2;
//                }
//            }
//        }

        //没有找到周期，也不收敛，按照震荡处理；实际情况也可能是迭代次数过少
#if DEBUG
        debug_result(cq, z, MAX_ITER, cycle_state::shock);
#endif
        state = cycle_state::shock;
        return MAX_ITER;
    }

    void draw_pixel(const vec2& pos, const color3 col){
        if(STEP == 1){
            draw_point(pos, col);
        } else{
            fill_circle(pos, 1.0 + STEP / 2,col);
        }
    }

    void draw_julia(const vec2& min, const vec2& max, const complexd& zc, cycle_state state)
    {
        // 将img的像素映射到（min, max）之间，得到z0
        // 逐个测试z0的收敛情况。
        for (int x = 0; x < p_img->width; x += STEP) {
            for (int y = 0; y < p_img->height; y += STEP) {
                auto z0 = complexd(
                        lerp(min.x, max.x, x / float(p_img->width-1)),
                        lerp(min.y, max.y, y / float(p_img->height-1))
                        );
                test_julia_res(z0, zc, state);
                if(state != cycle_state::divergency &&
                    state != cycle_state::shock){
                    draw_pixel({x, y}, get_julia_col(state));
                }
            }
        }
    }

    void draw_mandelbrot(const vec2& min, const vec2& max)
    {
        // 将img的像素映射到（min, max）之间，得到z0
        // 逐个测试z0的收敛情况。
        for (int x = 0; x < p_img->width; x += STEP) {
            for (int y = 0; y < p_img->height; y += STEP) {
                auto z0 = complexd(
                        lerp(min.x, max.x, x / double (p_img->width-1)),
                        lerp(min.y, max.y, y / double(p_img->height-1))
                );
                cycle_state state = cycle_state::divergency;
                auto res = test_julia_res(z0, z0, state);
                draw_point({x, y}, get_julia_col(state, res));
            }
        }
    }
};

#endif //SRC_DRAWER_COMPLEX_H
